{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Connecting Networks\n",
    "**scikit-rf** supports the connection of arbitrary ports of N-port networks. It accomplishes this using an algorithm called sub-network growth[[1]](#References), available through the function `connect()`. Note that this function takes into account port impedances. If two connected ports have different port impedances, an appropriate impedance mismatch is inserted. This capability is illustrated here with situations often encountered."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import skrf as rf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cascading 2-port and 1-port Networks\n",
    "A common problem is to connect two Networks one to the other, also known as cascading Networks, which creates a new Network. The figure below illustrates sile simple situations, where the port numbers are identified in gray:\n",
    "\n",
    "<img src=\"figures/networks_connecting_2_2ports.svg\" width=\"600\">\n",
    "\n",
    "or,\n",
    "\n",
    "<img src=\"figures/networks_connecting_2port_1port.svg\" width=\"600\">\n",
    "\n",
    "\n",
    "Let's illustrate this by connecting a transmission line (2-port Network) to a short-circuit (1-port Network) to create a delay short (1-port Network):\n",
    "\n",
    "<img src=\"figures/networks_delay_short.svg\" width=\"600\">\n",
    "\n",
    "Cascading Networks being a frequent operation, it can done conveniently through the `**` operator or with the `cascade` function: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "line = rf.data.wr2p2_line  # 2-port\n",
    "short = rf.data.wr2p2_short  # 1-port\n",
    "\n",
    "delayshort = line ** short  # --> 1-port Network\n",
    "print(delayshort)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "or, equivalently using the `cascade()` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "delayshort2 = rf.cascade(line, short)\n",
    "print(delayshort2 == delayshort)  # the result is the same"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is of course possible to connect two 2-port Networks together using the `connect()` function. The `connect()` function requires the Networks and the port numbers to connect together. In our example, the port 1 of the line is connected to the port 0 of the short: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "delayshort3 = rf.connect(line, 1, short, 0)\n",
    "print(delayshort3 == delayshort)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One often needs to cascade a chain Networks together:\n",
    "\n",
    "<img src=\"figures/networks_connecting_N_2ports.svg\" width=\"700\">\n",
    "or, \n",
    "<img src=\"figures/networks_connecting_N_2ports_1port.svg\" width=\"700\">\n",
    "\n",
    "\n",
    "which can be realized using chained `**` or the convenient function `cascade_list`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "line1 = rf.data.wr2p2_line  # 2-port\n",
    "line2 = rf.data.wr2p2_line  # 2-port\n",
    "line3 = rf.data.wr2p2_line  # 2-port\n",
    "line4 = rf.data.wr2p2_line  # 2-port\n",
    "short = rf.data.wr2p2_short  # 1-port\n",
    "\n",
    "chain1 = line1 ** line2 ** line3 ** line4 ** short\n",
    "\n",
    "chain2 = rf.cascade_list([line1, line2, line3, line4, short])\n",
    "\n",
    "print(chain1 == chain2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cascacing 2N-port Networks"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The cascading operator `**` also works for to 2N-port Networks, width the following port scheme: \n",
    "\n",
    "<img src=\"figures/networks_connecting_2_2Nports.svg\" width=\"600\">\n",
    "\n",
    "It also works for multiple 2N-port Network. For example, assuming you want to cascade three 4-port Network `ntw1`, `ntw2` and `ntw3`, you can use:\n",
    "```\n",
    "resulting_ntw = ntw1 ** ntw2 ** ntw3\n",
    "``` \n",
    "This is illustrated in [this example on balanced Networks](../examples/networktheory/Balanced%20Network%20De-embedding.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cascading Multi-port Networks\n",
    "To make specific connections between multi-port Networks, three solutions are available, which mostly depends of the complexity of the circuit one wants to build:\n",
    "\n",
    "* For reduced number of connection(s): the `connect()` function\n",
    "\n",
    "* For intermediate complexity, where you might need to connect multiple Networks in parallel to the same intersection, we offer the `parallelconnect()` method. This method provides a balance between the simplicity of `connect()` and the flexibility of `Circuit` object. For more information, please refer to the [`paralleconnect()` documentation](../api/generated/skrf.network.paralleconnect.html#skrf.network.paralleconnect)\n",
    "\n",
    "* For more advanced connections between many arbitrary N-port Networks, the `Circuit` object is more relevant since it allows defining explicitly the connections between ports and Networks. For more information, please refer to the [Circuit documentation](Circuit.ipynb). \n",
    "\n",
    "As an example, terminating one of the port of an a 3-port Network, such as an ideal 3-way splitter:\n",
    "\n",
    "<img src=\"figures/networks_connecting_3port_1port.svg\" width=\"600\">\n",
    "\n",
    "can be done like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tee = rf.data.tee"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To connect port `1` of the tee, to port `0` of the delay short,\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminated_tee = rf.connect(tee, 1, delayshort, 0)\n",
    "terminated_tee"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`parallelconnect()` method also could handle this situation with just a slight change in syntax.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminated_tee_par = rf.parallelconnect([tee, delayshort], [1, 0])\n",
    "terminated_tee_par"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the previous example, the port #2 of the 3-port Network `tee` becomes the port #1 of the resulting 2-port Network. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiple Connections of Multi-port Networks\n",
    "Keeping track of the port numbering when using multiple time the `connect` function can be tedious (this is the reason why the [Circuit object](Circuit.ipynb) can be simpler to use).\n",
    "\n",
    "Let's illustrate this with the following example: connecting the port #1 of a tee-junction (3-port) to the port #0 of a transmission line (2-port):\n",
    "\n",
    "<img src=\"figures/networks_connecting_3port_2port.svg\" width=\"600\">\n",
    "\n",
    "To keep track of the port scheme after the connection operation, let's change the port characteristic impedances (in red in the figure above):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tee.z0 = [1, 2, 3]\n",
    "line.z0 = [10, 20]\n",
    "# the resulting network is:\n",
    "rf.connect(tee, 1, line, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Networks Connections from the Intersection Perspective\n",
    "In the previous example, we briefly introduced the `parallelconnect()` method. In this section, We will detail the application scenarios of the `parallelconnect()` method with multiple examples and provide recommendations for comparing the three solutions.\n",
    "\n",
    "Firstly, let's consider the simplest example: inner-connecting any two ports within an N-port `Network`. This will result in an (N-2)-port `Network`.\n",
    "\n",
    "<img src=\"figures/networks_innerconnect_2_port.svg\" width=\"600\">\n",
    "\n",
    "Here, we can use the `innerconnect()` method to connect any two ports within the N-port `Network`\n",
    "\n",
    "```\n",
    "# Innerconnect the m'th and n'th ports of the N-Port Network\n",
    "inner_network = rf.innerconnect(nport_network, m, n)\n",
    "```\n",
    "\n",
    "`parallelconnect()` method could handle inner-connect situation like this:\n",
    "\n",
    "```\n",
    "inner_network_par = rf.parallelconnect(nport_network, [[m, n]])\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you anticipate the need to inner-connect more than 2 ports, the `innerconnect()` method will not suffice for this task. You'll need to look into using `tee` (`splitter`) or `Circuit` object to achieve your goal. However, `parallelconnect()` offers a straightforward solution for such cases,\n",
    "\n",
    "```\n",
    "# An example of inner-connect a list of ports of a N-port Network\n",
    "ports_list = [[m, n, ..., y, z]]\n",
    "inner_network2_par = rf.parallelconnect(nport_network, ports_list)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The second example involves connecting two multi-port Networks. As this cases has been previously demonstrated, we will not delve into it again here.\n",
    "\n",
    "Moving on, let's consider the parallel connection of multiple multi-port `Networks`. A common application of this is in the construction of a `T-type filter circuit`, figure taken from [electroniclinic.com](https://www.electroniclinic.com/filter-circuit-and-need-of-filters-in-electronics/#google_vignette)\n",
    "\n",
    "![T-type filter circuit](figures/t_type_filter.png)\n",
    "\n",
    "Let's ignore the specific details and just compare the three methods in implementing this example, you can use:\n",
    "\n",
    "```\n",
    "# 1. Connect() method with tee\n",
    "t_type_filter_ntwk = rf.connect(L1, 1, tee, 0)\n",
    "t_type_filter_ntwk = rf.connect(t_type_filter_ntwk, 1, C, 0)\n",
    "t_type_filter_ntwk = rf.connect(t_type_filter_ntwk, 2, L2, 0)\n",
    "t_type_filter_ntwk\n",
    "\n",
    "# 2. Circuit object method\n",
    "cnx = [\n",
    "    [(port1, 0), (L1, 0)],\n",
    "    [(port2, 0), (C , 1)],\n",
    "    [(port3, 0), (L2, 1)],\n",
    "    [(L1, 1), (C, 0), (L2, 0)]\n",
    "]\n",
    "t_type_filter_ckt = rf.Circuit(cnx)\n",
    "t_type_filter_ntwk = t_type_filter_ckt.network\n",
    "t_type_filter_ntwk\n",
    "\n",
    "# 3. Parallelconnect() method\n",
    "t_type_filter_ntwk = rf.parallelconnect([L1, C, L2], [1, 0, 0])\n",
    "t_type_filter_ntwk\n",
    "```\n",
    "\n",
    "It can be seen that the `parallelconnect()` method can realize the construction of `T-type filter` circuit very clearly and concisely."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this section, we compared the `parallelconnect()` method and compared it with the `connect()/innerconnect()` method and the `Circuit` object for connecting `Networks` from an intersection perspective.\n",
    "\n",
    "The `parallelconnect()` method excels in handling multiple parallel connections with minimal effort, making it ideal for scenarios requiring simplicity and efficiency. In contrast, the `connect()/innerconnect()` method is better suited for simpler, sequential connections, while the `Circuit` object is for complex, multi-layered connections with greater control.\n",
    "\n",
    "From the intersection perspective, the `Circuit` object is best for managing complex networks with multiple intersections, a task that exceeds the capabilities of other methods, which are designed for single intersection scenarios. And `innerconnect()/connect()` methods are limited to handling connections between individual or pairs of Networks, `parallelconnect()` removes the restriction on the number of Networks and can efficiently establish connections for multiple Networks in a single line of code, making it particularly advantageous for complex circuits with parallel connections at multiple points."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "\n",
    "[1] Compton, R.C.; , \"Perspectives in microwave circuit analysis,\" Circuits and Systems, 1989., Proceedings of the 32nd Midwest Symposium on , vol., no., pp.716-718 vol.2, 14-16 Aug 1989. URL: http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=101955&isnumber=3167"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
